// 泛型的约束
type A<U> = {
    a:U[]
}
// type B<T extends A<number> = {  //这里的T extends A,意识是传入的泛型类型，必须包含A对象类型里的所以属性
//     c:T;
//     d:T
// }
// const b:B<{a:number[],b:number}> = {
//     c:{a:1,b:1},                                                                        
//     d:{a:2,b:2}
// }

function combine<Type>(arr1: Type[], arr2: Type[]): Type[] { //声明函数指定泛型
    return arr1.concat(arr2);
  }

  //const arr = combine([1, 2, 3], ["hello"]);  //调用函数，不传泛型类型，报错，因为ts根据你传的第一个参数把泛型类型推定为number，后一个类型不符合泛型约束，ps:泛型就是用来约束变量的类型之间的关系的
  
  const arr = combine<string | number>([1, 2, 3], ["hello"]);//这里手动添加一个类型给泛型，就=没问题

//   规则：如果可能，使用类型参数本身而不是约束它
// 尽可能少使用泛型约束----<T extends A>这种写法


function filter2<Type, Func extends (arg: Type) => boolean>(
    arr: Type[],
    func: Func
  ): Type[] {
    return arr.filter(func);
  }

  //这里的泛型类型Func只在func参数的类型定义中使用了一次，故没必要定义这个泛型参数，单个泛型参数至少要在两个地方用到，否则就不该声明，
//   在定义泛型参数时，尽可能少定义泛型参数，参数越多，越复杂难懂

function f(x?: number) {
    // ...
  }
//   等价于
function f1(x:number|undefined) {

}


function fn(x: string): string;
// Return type isn't right
function fn(x: number): boolean;
function fn(x: string | number):boolean|string {
  return "oops";
}